package cn.xiaopengstack.infrastructure.persistent.repository;

import cn.xiaopengstack.domain.activity.model.entity.RaffleActivityEntity;
import cn.xiaopengstack.domain.activity.repository.IRaffleActivityRepository;
import cn.xiaopengstack.domain.strategy.model.entity.StrategyAwardEntity;
import cn.xiaopengstack.domain.strategy.model.entity.StrategyEntity;
import cn.xiaopengstack.domain.strategy.model.entity.StrategyRuleEntity;
import cn.xiaopengstack.domain.strategy.model.valobj.*;
import cn.xiaopengstack.domain.strategy.repository.IStrategyRepository;
import cn.xiaopengstack.infrastructure.persistent.dao.*;
import cn.xiaopengstack.infrastructure.persistent.po.*;
import cn.xiaopengstack.infrastructure.persistent.redis.IRedisService;
import cn.xiaopengstack.types.common.Constants;
import cn.xiaopengstack.types.enums.ResponseCode;
import cn.xiaopengstack.types.exception.AppException;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RQueue;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 策略仓库
 * @author jiangyangang
 */
@Repository
@Slf4j
public class StrategyRepository implements IStrategyRepository {

    @Resource
    private IRedisService redisService;

    @Resource
    private IStrategyAwardDao strategyAwardDao;

    @Resource
    private IStrategyDao strategyDao;

    @Resource
    private IStrategyRuleDao strategyRuleDao;

    @Resource
    private IRuleTreeDao ruleTreeDao;
    @Resource
    private IRuleTreeNodeDao ruleTreeNodeDao;
    @Resource
    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;
    @Resource
    private IRaffleActivityRepository raffleActivityRepository;

    @Override
    public List<StrategyAwardEntity> queryStrategyAwardInfoList(Long strategyId) {
        // 从缓存取
        String redisKey = Constants.RedisKey.STRATEGY_AWARD_KEY + strategyId;
        List<StrategyAwardEntity> strategyAwardEntityListFromCache = redisService.getValue(Constants.RedisKey.STRATEGY_AWARD_KEY + strategyId);
        if (!CollectionUtils.isEmpty(strategyAwardEntityListFromCache)) {
            return strategyAwardEntityListFromCache;
        }

        // 从数据库取
        List<StrategyAward> strategyAwards = strategyAwardDao.queryStrategyAwardList(strategyId);
        List<StrategyAwardEntity> strategyAwardEntityList = strategyAwards.stream().map((strategyAward) -> StrategyAwardEntity.builder()
                .id(strategyAward.getId())
                .strategyId(strategyAward.getStrategyId())
                .awardId(strategyAward.getAwardId())
                .awardTitle(strategyAward.getAwardTitle())
                .awardSubtitle(strategyAward.getAwardSubtitle())
                .awardCount(strategyAward.getAwardCount())
                .awardCountSurplus(strategyAward.getAwardCountSurplus())
                .awardRate(strategyAward.getAwardRate())
                .ruleModels(strategyAward.getRuleModels())
                .sort(strategyAward.getSort())
                .createTime(strategyAward.getCreateTime())
                .updateTime(strategyAward.getUpdateTime())
                .build()).collect(Collectors.toList());

        // 保存至缓存
        redisService.setValue(redisKey, strategyAwardEntityList);

        return strategyAwardEntityList;
    }

    @Override
    public void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) {
        // 1、存储抽奖策略范围值，如10000，用于生成1000以内的随机数
        redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange);

        // 2. 存储概率查找表
        Map<Integer, Integer> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
        cacheRateTable.putAll(strategyAwardSearchRateTable);
    }

    @Override
    public Integer getRateRange(Long strategyId) {
        return getRateRange(String.valueOf(strategyId));
    }

    @Override
    public Integer getStrategyAwardAssemble(Long strategyId, Integer rateKey) {
        return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + strategyId, rateKey);
    }

    @Override
    public int getRateRange(String key) {
        String cacheKey = Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key;
        if (!redisService.isExists(cacheKey)) {
            throw new AppException(ResponseCode.STRATEGY_RULE_RANGE_NULL.getCode(), ResponseCode.STRATEGY_RULE_RANGE_NULL.getInfo());
        }

        return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key);
    }

    @Override
    public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {
        // 优先从缓存获取
        String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
        StrategyEntity strategyEntity = redisService.getValue(cacheKey);
        if (null != strategyEntity) return strategyEntity;
        Strategy strategy = strategyDao.queryStrategyByStrategyId(strategyId);
        strategyEntity = StrategyEntity.builder()
                .strategyId(strategy.getStrategyId())
                .strategyDesc(strategy.getStrategyDesc())
                .ruleModels(strategy.getRuleModels())
                .build();
        redisService.setValue(cacheKey, strategyEntity);
        return strategyEntity;

    }

    @Override
    public StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel) {
        StrategyRule strategyRule = new StrategyRule();
        strategyRule.setStrategyId(strategyId);
        strategyRule.setRuleModel(ruleModel);
        StrategyRule strategyRuleSelect = strategyRuleDao.queryStrategyRule(strategyRule);
        assert strategyRuleSelect != null;
        return StrategyRuleEntity.builder()
                .strategyId(strategyId)
                .ruleDesc(strategyRuleSelect.getRuleDesc())
                .ruleType(strategyRuleSelect.getRuleType())
                .ruleValue(strategyRuleSelect.getRuleValue())
                .ruleModel(strategyRuleSelect.getRuleModel())
                .awardId(strategyRuleSelect.getAwardId()).build();
    }

    @Override
    public StrategyAwardEntity queryStrategyAward(Long strategyId, Integer awardId) {
        StrategyAward strategyAward = strategyAwardDao.queryStrategyAward(strategyId, awardId);
        StrategyAwardEntity strategyAwardEntity = new StrategyAwardEntity();

        BeanUtils.copyProperties(strategyAward, strategyAwardEntity);

        return strategyAwardEntity;
    }

    @Override
    public String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel) {
        StrategyRule strategyRule = new StrategyRule();
        strategyRule.setStrategyId(strategyId);
        strategyRule.setAwardId(awardId);
        strategyRule.setRuleModel(ruleModel);
        return strategyRuleDao.queryStrategyRuleValue(strategyRule);
    }

    @Override
    public String queryStrategyRuleValue(Long strategyId, String ruleModel) {
        return queryStrategyRuleValue(strategyId, null, ruleModel);
    }

    @Override
    public StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Integer awardId) {
        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);
        strategyAward.setAwardId(awardId);
        String ruleModels = strategyAwardDao.queryStrategyAwardRuleModels(strategyAward);
        return StrategyAwardRuleModelVO.builder().ruleModels(ruleModels).build();
    }


    @Override
    public RuleTreeVO queryRuleTreeVOByTreeId(String treeId) {

        // 从缓存获取
        String cacheKey = Constants.RedisKey.RULE_TREE_VA_KEY  + treeId;
        RuleTreeVO ruleTreeVoCache = redisService.getValue(cacheKey);
        if (Objects.nonNull(ruleTreeVoCache)) {
            return ruleTreeVoCache;
        }

        RuleTree ruleTree = ruleTreeDao.queryByTreeId(treeId);
        List<RuleTreeNode> ruleTreeNodes = ruleTreeNodeDao.queryListByTreeId(treeId);
        List<RuleTreeNodeLine> ruleTreeNodeLines = ruleTreeNodeLineDao.queryListByTreeId(treeId);

        RuleTreeVO ruleTreeVO = new RuleTreeVO();
        ruleTreeVO.setTreeDesc(ruleTree.getTreeDesc());
        ruleTreeVO.setTreeName(ruleTree.getTreeName());
        ruleTreeVO.setTreeRootRuleNode(ruleTree.getTreeId());

        Map<String, RuleTreeNodeVO> treeNodeMap = ruleTreeNodes.stream().collect(Collectors.toMap(RuleTreeNode::getRuleKey, (node) -> {
            RuleTreeNodeVO ruleTreeNodeVO = new RuleTreeNodeVO();
            ruleTreeNodeVO.setRuleDesc(node.getRuleDesc());
            ruleTreeNodeVO.setRuleKey(node.getRuleKey());
            ruleTreeNodeVO.setTreeId(treeId);
            ruleTreeNodeVO.setRuleValue(node.getRuleValue());

            List<RuleTreeNodeLineVO> nodeLineVoList = ruleTreeNodeLines.stream()
                    .peek((l) -> {
                        log.info("当前结点连线 {} {}", JSONObject.toJSONString(l), node.getRuleKey());
                    })
                    .filter((nodeLine) -> nodeLine.getRuleNodeFrom().equals(node.getRuleKey()))
                    .map((selectNodeLine) -> {
                        RuleTreeNodeLineVO nodeLineVO = new RuleTreeNodeLineVO();
                        nodeLineVO.setTreeId(treeId);
                        nodeLineVO.setRuleNodeTo(selectNodeLine.getRuleNodeTo());
                        nodeLineVO.setRuleNodeFrom(selectNodeLine.getRuleNodeFrom());
                        nodeLineVO.setRuleLimitType(RuleLimitTypeVO.of(selectNodeLine.getRuleLimitType()));
                        nodeLineVO.setRuleLimitValue(RuleLogicCheckTypeVO.of(selectNodeLine.getRuleLimitValue()));
                        return nodeLineVO;
                    }).collect(Collectors.toList());

            ruleTreeNodeVO.setTreeNodeLineVOList(nodeLineVoList);
            return ruleTreeNodeVO;
        }));

        ruleTreeVO.setTreeNodeMap(treeNodeMap);
        ruleTreeVO.setTreeRootRuleNode(ruleTree.getTreeNodeRuleKey());

        log.info("规则树组装结果 {}", JSONObject.toJSONString(ruleTreeVO));
        redisService.setValue(cacheKey, ruleTreeVO);
        return ruleTreeVO;
    }

    @Override
    public void cacheStrategyAwardStock(AssembleAwardStockVO awardStockVO) {
        String key = String.format(Constants.RedisKey.STRATEGY_AWARD_STOCK_KEY, awardStockVO.getStrategyId(), awardStockVO.getAwardId());
        redisService.setAtomicLong(key, awardStockVO.getStock());
    }

    @Override
    public boolean decrStrategyAwardStock(StrategyAwardDecrStockVO stockVO) {
        String key = String.format(Constants.RedisKey.STRATEGY_AWARD_STOCK_KEY, stockVO.getStrategyId(), stockVO.getAwardId());

        // 这里逻辑可以做成LUA脚本
        if (Objects.isNull(redisService.getAtomicLong(key))) {
            log.warn("奖品库存扣减-扣减失败! 无效库存信息； 策略:{}, 奖品:{}", stockVO.getStrategyId(), stockVO.getAwardId());
            return false;
        }

        // 自减1
        long stock = redisService.getAtomicLong(key);

        long decr = stock - stockVO.getDecrStock();

        // 库存为0
        if (decr < 0) {
            log.warn("奖品库存扣减-扣减失败! 库存耗尽； 策略:{}, 奖品:{}", stockVO.getStrategyId(), stockVO.getAwardId());
            redisService.setAtomicLong(key, 0L);
            return false;
        }
        // 重新设置扣除后的缓存
        redisService.setAtomicLong(key, decr);

        String keyStockLock = String.format(Constants.RedisKey.STRATEGY_AWARD_STOCK_DECR_KEY, stockVO.getStrategyId(), stockVO.getAwardId(), decr);

        Boolean lock = redisService.setNx(keyStockLock);

        if (!lock) {
            log.warn("奖品库存扣减-扣减失败！命中无效库存锁； 策略:{}, 奖品:{}, key:{}", stockVO.getStrategyId(), stockVO.getAwardId(), keyStockLock);
        }

        return lock;
    }

    @Override
    public void produceStrategyAwardDecrStockQueue(AwardStockQueueVO queueVO) {
        String queueKey = String.format(Constants.RedisKey.STRATEGY_AWARD_STOCK_QUEUE_KEY, queueVO.getStrategyId(), queueVO.getAwardId());

        // 这里逻辑可以做成LUA脚本

        // 获取队列
        RBlockingQueue<AwardStockQueueVO> blockQueue = redisService.getBlockingQueue(queueKey);
        RDelayedQueue<AwardStockQueueVO> delayedQueue = redisService.getDelayedQueue(blockQueue);
        // 延迟3秒插入队列
        delayedQueue.offer(queueVO, 1L, TimeUnit.SECONDS);

        // 加入注册表
        String tableKey = Constants.RedisKey.STRATEGY_AWARD_STOCK_REGISTER_QUEUE_TABLE_KEY;
        redisService.addToSet(tableKey, JSONObject.toJSONString(queueVO));
    }

    @Override
    public AwardStockQueueVO consumeStrategyAwardDecrStockQueue(AwardStockQueueVO queueVO) {
        // 获取队列元素
        String queueKey = String.format(Constants.RedisKey.STRATEGY_AWARD_STOCK_QUEUE_KEY, queueVO.getStrategyId(), queueVO.getAwardId());
        RBlockingQueue<AwardStockQueueVO> destinationQueue = redisService.getBlockingQueue(queueKey);
        return destinationQueue.poll();
    }

    @Override
    public void updateStrategyAwardStock(UpdateAwardStockVO updateAwardStockVO) {
        int result = strategyAwardDao.updateStrategyAwardStock(updateAwardStockVO.getStrategyId(), updateAwardStockVO.getAwardId(), updateAwardStockVO.getDecrStock());
    }

    @Override
    public Set<AwardStockQueueVO> getCacheAwardRegisterTable() {
        String tableKey = Constants.RedisKey.STRATEGY_AWARD_STOCK_REGISTER_QUEUE_TABLE_KEY;
        Set<String> tableList = redisService.getSet(tableKey);
        if (CollectionUtils.isEmpty(tableList)) {
            return Collections.emptySet();
        }

        return tableList.stream()
                .map(value -> JSONObject.parseObject(value, AwardStockQueueVO.class))
                .collect(Collectors.toSet());
    }

    @Override
    public List<StrategyAwardEntity> queryStrategyAwardInfoListByActivityId(Long activityId) {
        RaffleActivityEntity activityEntity = raffleActivityRepository.queryRaffleActivityByActivityId(activityId);
        if (Objects.isNull(activityEntity)) {
            return Collections.emptyList();
        }

        return this.queryStrategyAwardInfoList(activityEntity.getStrategyId());
    }
}
